﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Anagramme.Contracts;
using System.ServiceModel;
using Anagramme.Server.Model;
using System.IO;
using System.Globalization;
using System.Collections.ObjectModel;

namespace Anagramme.Server.Service
{
    /// <summary>
    /// Service de jeu
    /// </summary>
    [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)]
    public sealed class GameService : ServiceBase<IGameServiceCallback>, IGameService
    {
        #region Constants
#if DEBUG
        private const string serveurThreadName = "server.vshost";
#else
        private const string serveurThreadName = "server";
#endif
        private const string dictionaryFileName = "dictionary.xml";
        #endregion

        #region Static Fields
        private static Random _rndGenerator = new Random();
        private static Dictionary _dictionary = CreateDictionary();
        #endregion

        #region static Methods
        private static Dictionary CreateDictionary()
        {
            Dictionary dictionary;
            string path = Path.Combine(Environment.CurrentDirectory, dictionaryFileName);
            using (StreamReader rd = new StreamReader(path))
            {
                DictionarySerializer sr = new DictionarySerializer();
                dictionary = (Dictionary)sr.Deserialize(rd);
            }
            return dictionary;
        }

        private static string CleanOrthography(string orthography)
        {
            string normalizedOrthography = new string(orthography
                .ToUpperInvariant()
                .Normalize(System.Text.NormalizationForm.FormD)
                .ToCharArray()
                .Where(c => CharUnicodeInfo.GetUnicodeCategory(c) == UnicodeCategory.UppercaseLetter)
                .ToArray());

            return normalizedOrthography;
        }

        private static List<WordInfo> ComputeAnagrammes(List<WordInfo> words)
        {
            List<WordInfo> anagrammes = new List<WordInfo>();

            foreach (WordInfo wordInfo in words)
            {
                string pattern = ComputePattern(wordInfo.Value);
                List<Word> filteredWords = _dictionary.Words.Where(w => w.Pattern == pattern).ToList();
                string word = string.Empty;

                do
                {
                    int k = _rndGenerator.Next(filteredWords.Count);
                    word = filteredWords[k].Orthography;
                } while (wordInfo.Value == word);

                anagrammes.Add(new WordInfo(word));
            }

            return anagrammes;
        }

        private static string ComputePattern(string orthography)
        {
            string normalizedOrthography = CleanOrthography(orthography);
            string pattern = new string(normalizedOrthography
                .ToCharArray()
                .OrderBy(c => c).ToArray());
            return pattern;
        }

        private static List<WordInfo> ComputeWords(int wordsLength)
        {
            List<WordInfo> words = new List<WordInfo>();

            List<Word> filteredWords = _dictionary.Words.Where (w => w.Length == wordsLength).ToList();
            for (int i = 0; i < Properties.Settings.Default.WordsCount; i++)
            {
                string word = string.Empty;
                string pattern = string.Empty;
                do
                {
                    int k = _rndGenerator.Next(filteredWords.Count);
                    word = filteredWords[k].Orthography;
                    pattern = filteredWords[k].Pattern;
                } while (words.Select(w => ComputePattern(w.Value)).Contains(pattern));

                words.Add(new WordInfo(word));
            }

            return words;
        }
        #endregion

        #region IGameService Implementation
        /// <summary>
        /// Authentifie le client sur le service
        /// </summary>
        /// <param name="token">Jeton de sécurité</param>
        /// <remarks>Le jeton de sécurité est obtenu lors de la connexion au service de lobby</remarks>
        public void Authenticate(Guid token)
        {
            if (Context.Players.Select(p => p.Token).Contains(token))
            {
                base.LogOn(token);
            }
        }

        /// <summary>
        /// Envoie d'une demande pour récupérer les informations sur l'équipe Bleue
        /// </summary>
        public void RequestGameBlueTeamInfos()
        {
            ClientCallback.ResponseGameBlueTeamInfos(Context.BlueTeam);
        }

        /// <summary>
        /// Envoie d'une demande d'extinction du serveur
        /// </summary>
        /// <remarks>Nécessite d'être le maitre du lobby</remarks>
        public void RequestGameClose()
        {
            foreach (IGameServiceCallback callback in base.ClientCallbacks)
            {
                callback.ResponseGameDisconnect();
            }

            System.Diagnostics.Process[] processes = System.Diagnostics.Process.GetProcessesByName(serveurThreadName);

            foreach (System.Diagnostics.Process process in processes)
            {
                process.Kill();
            }
        }

        /// <summary>
        /// Envoie d'une demande pour récupérer les informations sur la partie
        /// </summary>
        public void RequestGameInfos()
        {
            GameInfo infos = new GameInfo()
            {
                CurrentRound = Context.GameInfo.CurrentRound,
                GameMode = Context.GameInfo.GameMode,
                Target = Context.GameInfo.Target
            };
            base.ClientCallback.ResponseGameInfos(infos);
        }

        /// <summary>
        /// Indique au serveur que le joueur quitte la partie
        /// </summary>
        public void RequestGameLeave()
        {
            if (!object.ReferenceEquals(base.Player, null))
            {
                foreach (IGameServiceCallback callback in base.OthersClientCallbacks)
                {
                    callback.ResponseGamePlayerLeave(base.Player.Id);
                }
            }
            base.LogOff();

            System.Diagnostics.Process[] processes = System.Diagnostics.Process.GetProcessesByName(serveurThreadName);

            foreach (System.Diagnostics.Process process in processes)
            {
                process.Kill();
            }
        }

        /// <summary>
        /// Indique au serveur que le jeu à été chargé sur le client
        /// </summary>
        public void RequestGameLoaded()
        {
            Context.PlayersReadyCount++;
            if (Context.PlayersReadyCount.Equals(Context.Players.Count()))
            {
                Context.State = ServerState.Playing;
                _rndGenerator = new Random();
                NewRound();
            }
        }

        /// <summary>
        /// Envoie d'une demande pour récupérer les informations sur l'équipe Rouge
        /// </summary>
        public void RequestGameRedTeamInfos()
        {
            ClientCallback.ResponseGameRedTeamInfos(Context.RedTeam);
        }

        /// <summary>
        /// Envoie d'une demande de soumission d'un mot
        /// </summary>
        /// <param name="wordNumber">Index du mot à contrôler</param>
        /// <param name="word">Mot à comparer</param>
        /// <remarks>La comparaison des mots n'est ni sensible à la casse, ni aux accents</remarks>
        [FaultContract(typeof(ArgumentNullException))]
        public void RequestGameSubmitWord(int wordNumber, string word)
        {
            if (object.ReferenceEquals(word, null))
            {
                throw new ArgumentNullException("word");
            }

            bool found = false;
            Collection<WordInfo> words = null;

            if (base.Player.Team == Context.BlueTeam)
            {
                words = Context.CurrentRound.RedWords;
            }
            else if (base.Player.Team == Context.RedTeam)
            {
                words = Context.CurrentRound.BlueWords;
            }
            if (CleanOrthography(words[wordNumber].Value.ToUpperInvariant()) == CleanOrthography(word.ToUpperInvariant()))
            {
                words[wordNumber].IsFound = true;
                found = true;
                int teamId = base.Player.Team.Id;

                foreach (IGameServiceCallback callback in base.OthersClientCallbacks)
                {
                    Player p = base.GetPlayerFromCallback(callback);
                    if (p.Team.Id.Equals(teamId))
                    {
                        this.ClientCallback.ResponseGameWordSubmit(wordNumber, found);
                    }
                    else
                    {
                        callback.ResponseGameWordFound(wordNumber);
                    }
                }
            }

            this.ClientCallback.ResponseGameWordSubmit(wordNumber, found);
            if (found)
            {
                if (Context.CurrentRound.RedWords.All(w => w.IsFound) || Context.CurrentRound.BlueWords.All(w => w.IsFound))
                {
                    Context.BlueTeam.Score += Context.CurrentRound.RedWords.Count(w => w.IsFound);
                    Context.RedTeam.Score += Context.CurrentRound.BlueWords.Count(w => w.IsFound);
                    foreach (IGameServiceCallback callback in base.ClientCallbacks)
                    {
                        callback.ResponseGameRoundFinished();
                        callback.ResponseGameIncreaseTeamScore(Context.RedTeam.Id, Context.RedTeam.Score);
                        callback.ResponseGameIncreaseTeamScore(Context.BlueTeam.Id, Context.BlueTeam.Score);
                    }
                    this.NewRound();
                }
            }
        }
        #endregion

        #region Methods
        private bool IsGameFinished()
        {
            bool isFinished = false;
            if (Context.GameInfo.GameMode == GameModes.RoundMode && Context.GameInfo.CurrentRound > Context.GameInfo.Target)
            {
                Context.ResultInfo = new ResultInfo()
                {
                    Winner = Context.RedTeam.Score > Context.BlueTeam.Score ? Context.RedTeam :
                    Context.RedTeam.Score < Context.BlueTeam.Score ? Context.BlueTeam :
                    null
                };
                isFinished = true;
            }
            else if (Context.GameInfo.GameMode == GameModes.ScoreMode)
            {
                int targetScore = Context.GameInfo.Target;
                if (Context.RedTeam.Score >= targetScore)
                {
                    Context.ResultInfo = new ResultInfo()
                    {
                        Winner = Context.RedTeam
                    };
                    isFinished = true;
                }
                else if (Context.BlueTeam.Score >= targetScore)
                {
                    Context.ResultInfo = new ResultInfo()
                    {
                        Winner = Context.BlueTeam
                    };
                    isFinished = true;
                }
            }

            return isFinished;
        }

        private void NewRound()
        {
            Context.GameInfo.CurrentRound++;

            bool isFinished = this.IsGameFinished();
            if (isFinished)
            {
                foreach (IGameServiceCallback callback in base.ClientCallbacks)
                {
                    callback.ResponseGameFinished();
                }
                base.Context.State = ServerState.GameFinished;
            }
            else
            {
                Context.CurrentRound = new RoundInfo();
                int wordLength = _rndGenerator.Next(Context.LobbyInfo.MinWordsLength, Context.LobbyInfo.MaxWordsLength + 1);
                Context.CurrentRound.WordsLength = wordLength;

                List<WordInfo> redWords = ComputeWords(Context.CurrentRound.WordsLength);
                List<WordInfo> blueWords = ComputeAnagrammes(redWords);
                redWords.ForEach(w => Context.CurrentRound.RedWords.Add(w));
                blueWords.ForEach(w => Context.CurrentRound.BlueWords.Add(w));

                RoundInfo redRound = new RoundInfo();
                redRound.WordsLength = Context.CurrentRound.WordsLength;
                redWords.ForEach(w => redRound.RedWords.Add(w));
                for (int i = 0; i < Properties.Settings.Default.WordsCount; i++)
                {
                    redRound.BlueWords.Add(new WordInfo());
                }

                RoundInfo blueRound = new RoundInfo();
                blueRound.WordsLength = Context.CurrentRound.WordsLength;
                blueWords.ForEach(w => blueRound.BlueWords.Add(w));
                for (int i = 0; i < Properties.Settings.Default.WordsCount; i++)
                {
                    blueRound.RedWords.Add(new WordInfo());
                }

                foreach (IGameServiceCallback callback in base.ClientCallbacks)
                {
                    Player player = base.GetPlayerFromCallback(callback);
                    RoundInfo round = player.Team.Id.Equals(Context.RedTeam.Id) ? redRound : blueRound;
                    callback.ResponseGameNewRound(round);
                }
            }
        }
        #endregion

        #region ServiceBase Overrides
        /// <summary>
        /// Méthode soulevée lorsque le callback du client passe à l'état Closing
        /// </summary>
        protected override void OnChannelClosing()
        {
            base.OnChannelClosing();
            if (!object.ReferenceEquals(this.Player, null) && Context.State == ServerState.Playing)
            {
                if (this.Player.IsLobbyMaster)
                {
                    this.RequestGameClose();
                }
                else
                {
                    this.RequestGameLeave();
                }
            }
        }
        #endregion
    }
}
